#include "FSBlock.h"
#include "BlockLinkedList.h"
#include "BlockGroupException.h"
#include "FreeList.h"
//#include "BlockGroup.h"
#include "IntArray.h"
#include "Directory.h"
#include "DirectoryEntry.h"
#include <iostream>
#include <string.h>
#define DEVICE_NAME_LENGTH  100

Directory::Directory(Disk* td, bool createDirectory) {
  this->the_disk = td;
  the_free_list   = new FreeList(the_disk, true);
  dir_block_group = the_free_list->CreateNew();
}

Directory::~Directory() {
    // The private variables the_disk and de_list should have their
    // destructors called automaticly. Therefore, there isan't anything here.
    delete the_free_list;
}

bool Directory::add_entry(DirectoryEntry new_de) {
    de_list.push_back(new_de);
    return true; // push_back doesn't seem to give any useful output
}

bool Directory::delete_entry(string search_key) {
    // Trim really long search_keys that might be problematic.
    if ( search_key.length() > FILE_NAME_MAX_CHARS - 1 ) {
        search_key = search_key.substr(0, FILE_NAME_MAX_CHARS - 1);
    }
    
    for (vector<DirectoryEntry>::iterator i = de_list.begin(); i != de_list.end(); i++) {
        if (i->file_name.compare(search_key) == 0) {
            de_list.erase(i);
            return true;
        }
    }

    // no DirectoryEntry was found 
    return false;
}

int Directory::get_num_DirectoryEntry() {
    return de_list.size();
}

int Directory::scandir(vector<DirectoryEntry> &result_set, string search_key) {
    // Iterator for a DirectoryEntry vector
    vector<DirectoryEntry>::iterator it;

    // when search_key is blank (""), show everything
    if (search_key.compare("") == 0) {
        result_set = de_list;
    }
    else {

        for ( it = de_list.begin(); it != de_list.end(); it++ ) {
            DirectoryEntry another_de = *it;
            // if (another_de.file_name == search_key),
            // add the DirectoryEntry to result_set
            if (another_de.file_name.compare( search_key ) == 0) {
                result_set.push_back(another_de);
            }
        }
    }
    return result_set.size();
}

DirectoryEntry* Directory::scandir(string search_key) {
    for (vector<DirectoryEntry>::iterator i = de_list.begin(); i != de_list.end(); i++) {
        if (i->file_name.compare(search_key) == 0) {
            return &(*i);
        }
    }

    // no DirectoryEntry was found 
    return NULL;
}

bool Directory::rename_entry(string new_name, string old_name) {
    unsigned int the_FCB = 0;
    the_FCB = get_FCB(old_name); 
    delete_entry(old_name);
    DirectoryEntry new_DirectoryEntry(the_FCB, new_name);
    add_entry(new_DirectoryEntry);
    return true;
}

unsigned int Directory::get_FCB(string search_key) {
    DirectoryEntry *a_directory = scandir(search_key);
    if (a_directory) {
        return a_directory->FCB_block_number;
    }
    else
        return 0;
}

bool Directory::flushdir() {
    unsigned int fn_len= DirectoryEntry::DEFAULT_FILE_NAME_DATA_LENGTH;
   /* In general:
    *   - Write Directory Entries to Disk
    *       + Erase BlockGroup for Directory
    *       + Allocate new BlockGroup from the FreeList
    *       - Write DirectoryEntry's to disk (within the Block's made up of the
    *         BlockGroup).
    *   - Update Master Block (a BlockGroup starting at byte 0 on the disk).
    */
    unsigned int directory_entry_index = 0;
    unsigned char *a_buffer = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];

    // return dir_block_group to the FreeList
    //dir_block_group->AddBlock();
    printf("Number of blocks in dir_block_group: %d\n", dir_block_group->GetNumberOfBlocks());
    the_free_list->Return((BlockLinkedList *) dir_block_group);

    // Allocate new BlockGroup from the FreeList
    dir_block_group = the_free_list->CreateNew();

    /* Write Directory Entries to disk
     */
    // Figure out #blocks to hold all files (DirectoryEntrys) in the Directory.
    int num_blocks_needed_to_hold_directory = 0;
    int num_directory_entries_per_block          = 0;
    num_directory_entries_per_block = the_disk->blockSize() / DirectoryEntry::DEFAULT_DIRECTORY_ENTRY_SIZE;

    // Ceiling( DE_Count / num_directory_entries_per_block )
    num_blocks_needed_to_hold_directory = (this->get_num_DirectoryEntry()  + num_directory_entries_per_block - 1) / num_directory_entries_per_block;

    /* Debugging statements to ensure num_blocks_needed_to_hold_directory
     * is correct.
     */
    printf("Directory::flush blockSize: %d\n", the_disk->blockSize());
    printf("Directory::flush DEFAULT_DIRECTORY_ENTRY_SIZE: %d\n", DirectoryEntry::DEFAULT_DIRECTORY_ENTRY_SIZE);
    printf("Directory::flush num_directory_entries_per_block: %d\n", num_directory_entries_per_block);
    printf("Directory::flush num_blocks_needed_to_hold_directory: %d\n", num_blocks_needed_to_hold_directory);



    // Give our BlockGroup all the blocks we need to represent all the Directory Entries
    for (int i = 0; i < num_blocks_needed_to_hold_directory; i++) {
      if (dir_block_group->AddBlock() ) {
        printf("Directory::flush adding block %d\n", i);
      }
      else {
          //TODO: Might need to throw an exception as "Proper" Software Enginnering.
          printf("ERROR: Allocating block in dir_block_group\n");
      }
    }

    // Use rewind(), GetCurrentBlock, GetNextBlock to traverse
    // the BlockLinkedList.
 
    dir_block_group->Rewind();
   // This was given to Diane and Tim by cprince, but it doesn't work correctly
/*    for (FSBlock *block = dir_block_group->GetCurrentBlock();
         block != NULL;
         dir_block_group->GetNextBlock()) {
 */
        bool last_DE = false;
    for (;;) {
        bool first_DE_in_block = true;
        if (last_DE == true) {
            printf("Breakinging out last_DE\n");
            break;
        }
        FSBlock *block = dir_block_group->GetCurrentBlock();
        if (block == NULL) {
             break; // We're at the end of the BlockGroup
            printf("Breaking out");
        }
        
        if ( (directory_entry_index) > this->get_num_DirectoryEntry()) {
            printf("Breaking out of Outer loop %d > %d\n", (directory_entry_index), this->get_num_DirectoryEntry());
            break;
        }

        printf("Outer Loop\nShowing Current Block\n");
        block->Print();

            unsigned char* my_buffer = new unsigned char[512];
            unsigned char* my_buffer_pointer = my_buffer;
         // Every 4 DirectoryEntry's, move onto the next FSBlock
        for (;;) {


            printf("--Inner Loop\n");
            printf("  directory_entry_index: %d\n", directory_entry_index);
            printf("  num_directory_entries_per_block: %d\n", num_directory_entries_per_block);
            printf("  Number of DE in total: %d\n", this->get_num_DirectoryEntry());
            int the_mod = directory_entry_index % (num_directory_entries_per_block +1);
//            the_mod++;
            printf("  mod: %d\n", the_mod);

            if (( the_mod == (num_directory_entries_per_block ) ) && (first_DE_in_block == false)){
                printf("-Breaking because of mod...\n");
                //directory_entry_index++;
                first_DE_in_block = true;
                break;
            }

            if (directory_entry_index  >= this->get_num_DirectoryEntry()) {
                printf("Breaking because end in inner...\n");
                //directory_entry_index++;
                last_DE = true;
                break;
            }

/*            if (first_DE_in_block == true) {
                my_buffer_pointer = my_buffer_pointer + sizeof(directory_entry_index);
            }*/


            // TODO: Do stuff here
            const char * array_for_this_directory_entry_file_name = de_list[directory_entry_index].file_name.c_str();

            printf("  Filename: %s\n", array_for_this_directory_entry_file_name);
            memcpy(my_buffer_pointer + 4, array_for_this_directory_entry_file_name, fn_len);
            IntArray::SetInt(my_buffer_pointer, de_list[directory_entry_index].FCB_block_number, 0);

            

            // All done with DirectoryEntry
            directory_entry_index++;
            first_DE_in_block = false;
        }
        block->SetBuffer(my_buffer);
        //block->Print();
        printf("Writing FSBlock containing DirectoryEntrys...\n");
        block->Write(the_disk);
        delete [] my_buffer;
  // for ...
        // For <= 4 DirectoryEntrys:
        //   Set m_buffer (the datamember) of 'block' to have the file_name and FCB_block_number (using int_to_char())
        // Then Write 'block' to the_disk.
        // FSBlock.Write(Disk *disk)
        if (last_DE == true) {
            printf("Breakinging out last_DE at bottom\n");
            break;
        }
        printf(" Before Block Number: %d", block->GetBlockNumber());
        dir_block_group->GetNextBlock();
        printf(" After Block Number: %d", block->GetBlockNumber());
    }

    // TODO: update Master Block


}

void Directory::Test() {
    // Stolen, in part, from cprince's BlockLinkedList::Test()
    char deviceName[DEVICE_NAME_LENGTH] = "disk1";
    unsigned long numberOfBlocks = 100;
    unsigned int blockSize = Disk::DEFAULT_BLOCK_SIZE;
//    FreeList *mother = NULL;
    int cmd = 0;
    Disk *disk1 = NULL;
    bool physicalDevice = false;

    disk1  = new Disk(deviceName, numberOfBlocks, blockSize, physicalDevice);
//    mother = new FreeList(disk1, true);

    Directory the_dir(disk1, false);

    printf("Directory::Test Making a directory entry...\n");
    DirectoryEntry a_de(18, "one");
    std::cout << a_de.FCB_block_number << endl;
    cout << "Test\n";

    
    cout << "Number of DirectoryEntrys " << the_dir.get_num_DirectoryEntry() << endl;
    printf("Directory::Test Pushing DirectoryEntry into Directory...\n");
    the_dir.add_entry(a_de);
    cout << "Number of DirectoryEntrys " << the_dir.get_num_DirectoryEntry() << endl;

    DirectoryEntry b_de(32, "two");
    the_dir.add_entry(b_de);
    DirectoryEntry c_de(14, "three");
    the_dir.add_entry(c_de);
    DirectoryEntry d_de(14, "four");
    the_dir.add_entry(d_de);
    DirectoryEntry e_de(14, "five");
    the_dir.add_entry(e_de);

    vector<DirectoryEntry> results;
    cout << the_dir.scandir(results, "") << endl;

    //the_dir.delete_entry("hello");
    cout << the_dir.scandir(results, "") << endl;

    cout << the_dir.get_FCB("hello") << endl;
//    the_dir.rename_entry("three used to be here", "three");
    cout << the_dir.get_FCB("hello") << endl;
    cout << the_dir.get_FCB("three used to be here") << endl;
    cout << "Number of DirectoryEntrys " << the_dir.get_num_DirectoryEntry() << endl;
    the_dir.flushdir();



    // Delete objects created, prevents memory leaks
 //   delete mother;
    delete disk1;
}
